import {
  Shape,
  type AnchorPoint,
  type Bounds,
  type Point,
  type ShapeProperties,
  type StateProvider,
} from './Shape';

export class Path extends Shape {
  public points: Point[];
  public anchors: AnchorPoint[];

  constructor(
    x: number,
    y: number,
    color: string,
    lineWidth: number,
    stateProvider: StateProvider,
  ) {
    super(x, y, color, lineWidth, stateProvider);
    this.points = [{ x, y }];
    this.anchors = [];
  }

  public addPoint(x: number, y: number): void {
    this.points.push({ x, y });
  }

  public updateEnd(x: number, y: number): void {
    this.addPoint(x, y);
  }

  public draw(ctx: CanvasRenderingContext2D): void {
    const scale = this.stateProvider?.getScale() ?? 1;
    ctx.strokeStyle = this.color;
    ctx.lineWidth = this.lineWidth / scale;
    ctx.beginPath();

    const firstPoint = this.points[0];
    ctx.moveTo(firstPoint.x, -firstPoint.y);

    for (let i = 1; i < this.points.length; i++) {
      const point = this.points[i];
      ctx.lineTo(point.x, -point.y);
    }

    ctx.stroke();

    if (this.selected) {
      this.drawSelection(ctx);
    }
  }

  public updateProperty(prop: string, value: string): void {
    super.updateProperty(prop, value);
    this.updateSpecificProperty(prop, value);
  }

  public contains(x: number, y: number): boolean {
    const scale = this.stateProvider?.getScale() ?? 1;
    const hitDistance = (10 + this.lineWidth) / scale;

    for (let i = 0; i < this.points.length - 1; i++) {
      const p1 = this.points[i];
      const p2 = this.points[i + 1];

      const distance = this.pointToLineDistance(x, y, p1.x, p1.y, p2.x, p2.y);
      if (distance <= hitDistance) {
        return true;
      }
    }

    const firstPoint = this.points[0];
    const lastPoint = this.points[this.points.length - 1];
    const dx1 = x - firstPoint.x;
    const dy1 = y - firstPoint.y;
    const dx2 = x - lastPoint.x;
    const dy2 = y - lastPoint.y;
    const dist1 = Math.sqrt(dx1 * dx1 + dy1 * dy1);
    const dist2 = Math.sqrt(dx2 * dx2 + dy2 * dy2);

    return dist1 <= hitDistance || dist2 <= hitDistance;
  }

  private pointToLineDistance(
    px: number,
    py: number,
    x1: number,
    y1: number,
    x2: number,
    y2: number,
  ): number {
    const A = px - x1;
    const B = py - y1;
    const C = x2 - x1;
    const D = y2 - y1;

    const dot = A * C + B * D;
    const lenSq = C * C + D * D;
    let param = -1;
    if (lenSq !== 0) param = dot / lenSq;

    let xx: number;
    let yy: number;

    if (param < 0) {
      xx = x1;
      yy = y1;
    } else if (param > 1) {
      xx = x2;
      yy = y2;
    } else {
      xx = x1 + param * C;
      yy = y1 + param * D;
    }

    const dx = px - xx;
    const dy = py - yy;
    return Math.sqrt(dx * dx + dy * dy);
  }

  public getProperties(): ShapeProperties {
    let length = 0;
    for (let i = 0; i < this.points.length - 1; i++) {
      const p1 = this.points[i];
      const p2 = this.points[i + 1];
      const dx = p2.x - p1.x;
      const dy = p2.y - p1.y;
      length += Math.sqrt(dx * dx + dy * dy);
    }

    return {
      type: 'Path',
      color: this.color,
      lineWidth: this.lineWidth,
      position: {
        x: this.x.toFixed(1),
        y: this.y.toFixed(1),
      },
      startPoint: {
        x: this.points[0].x,
        y: this.points[0].y,
      },
      endPoint: {
        x: this.points[this.points.length - 1].x,
        y: this.points[this.points.length - 1].y,
      },
      pointCount: this.points.length,
      pathLength: length.toFixed(1),
      points: this.points.map((p) => ({
        x: p.x,
        y: p.y,
      })),
    };
  }

  public getBounds(): Bounds | null {
    if (this.points.length === 0) return null;

    let left = this.points[0].x;
    let right = this.points[0].x;
    let top = this.points[0].y;
    let bottom = this.points[0].y;

    for (const point of this.points) {
      left = Math.min(left, point.x);
      right = Math.max(right, point.x);
      top = Math.min(top, point.y);
      bottom = Math.max(bottom, point.y);
    }

    return { left, right, top, bottom };
  }

  public updateSpecificProperty(prop: string, value: string): void {
    if (prop.startsWith('point-')) {
      const index = Number.parseInt(prop.split('-')[1], 10);
      const coord = prop.split('-')[2];
      if (coord === 'x') {
        this.points[index].x = Number.parseFloat(value);
      } else if (coord === 'y') {
        this.points[index].y = Number.parseFloat(value);
      }
    }
  }

  public createAnchors(): void {
    const bounds = this.getBounds();
    if (!bounds || this.points.length === 0) return;

    this.anchors = [
      { x: bounds.left, y: bounds.top, type: 'nw' },
      { x: bounds.right, y: bounds.top, type: 'ne' },
      { x: bounds.right, y: bounds.bottom, type: 'se' },
      { x: bounds.left, y: bounds.bottom, type: 'sw' },
    ];
  }
}
